09. Testing Local Data Source
You just created unit tests for your TasksDao. In this step, you'll create integration tests for your TasksLocalDataSource. TasksLocalDatasource is a class that takes the information returned by the DAO and converts it to a format that is expected by your repository class (for example, it wraps returned values with Success or Error states). You'll be writing an integration test because you'll test both the real TasksLocalDatasource code and the real DAO code.
Step 1: Create an integration test for TasksLocalDataSource
The steps for creating tests for your TasksLocalDataSourceTest are very similar to creating the DAO tests.
- Open your app's
TasksLocalDataSourceclass. - Right-click on the
TasksLocalDataSourceclass name and select Generate, then Test. - Follow the prompts to create
TasksLocalDataSourceTestin the androidTest source set. - Copy the following code:
TasksLocalDataSourceTest.kt
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@MediumTest
class TasksLocalDataSourceTest {
// Executes each task synchronously using Architecture Components.
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
}
Notice that the only real difference between this and the DAO testing code is that the TasksLocalDataSource can be considered a medium "integration" test (as seen by the @MediumTest annotation), because the TasksLocalDataSourceTest will test both the code in TasksLocalDataSource and how it integrates with the DAO code.
- In
TasksLocalDataSourceTest, create alateinitfield for the two components you're testing–-TasksLocalDataSourceand yourdatabase:
TasksLocalDataSourceTest.kt
private lateinit var localDataSource: TasksLocalDataSource
private lateinit var database: ToDoDatabase
- Make a
@Beforemethod for initializing your database and datasource. - Create your database in the same way you did for your DAO test, using the
inMemoryDatabaseBuilderand theApplicationProvider.getApplicationContext()method - Add allowMainThreadQueries. Normally Room doesn't allow database queries to be run on the main thread. Calling
allowMainThreadQueriesturns off this check. Don't do this in production code! - Instantiate the
TasksLocalDataSource, using your database andDispatchers.Main. This will run your queries on the main thread (this is allowed because ofallowMainThreadQueries).
TasksLocalDataSourceTest.kt
@Before
fun setup() {
// Using an in-memory database for testing, because it doesn't survive killing the process.
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
ToDoDatabase::class.java
)
.allowMainThreadQueries()
.build()
localDataSource =
TasksLocalDataSource(
database.taskDao(),
Dispatchers.Main
)
}
- Make an
@Aftermethod for cleaning up your database usingdatabase.close().
The complete code should look like this:
TasksLocalDataSourceTest.kt
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@MediumTest
class TasksLocalDataSourceTest {
private lateinit var localDataSource: TasksLocalDataSource
private lateinit var database: ToDoDatabase
// Executes each task synchronously using Architecture Components.
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Before
fun setup() {
// Using an in-memory database for testing, because it doesn't survive killing the process.
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
ToDoDatabase::class.java
)
.allowMainThreadQueries()
.build()
localDataSource =
TasksLocalDataSource(
database.taskDao(),
Dispatchers.Main
)
}
@After
fun cleanUp() {
database.close()
}
}
Step 2: Write your first TasksLocalDataSourceTest
Let's copy and run an example test first.
- Copy these import statements:
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import com.example.android.architecture.blueprints.todoapp.data.succeeded
import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Test
- Copy this test:
TasksLocalDataSourceTest.kt
// runBlocking is used here because of https://github.com/Kotlin/kotlinx.coroutines/issues/1204
// TODO: Replace with runBlockingTest once issue is resolved
@Test
fun saveTask_retrievesTask() = runBlocking {
// GIVEN - A new task saved in the database.
val newTask = Task("title", "description", false)
localDataSource.saveTask(newTask)
// WHEN - Task retrieved by ID.
val result = localDataSource.getTask(newTask.id)
// THEN - Same task is returned.
assertThat(result.succeeded, `is`(true))
result as Success
assertThat(result.data.title, `is`("title"))
assertThat(result.data.description, `is`("description"))
assertThat(result.data.isCompleted, `is`(false))
}
This is very similar to your DAO test. Like the DAO test, this test:
- Creates a task and inserts it into the database.
- Retrieves the task using its id.
- Asserts that that task was retrieved, and that all its properties match the inserted task.
The only real difference from the analogous DAO test is that the local data source returns an instance of the sealed Result class, which is the format the repository expects. For example, this line here casts the result as a Success:
TasksLocalDataSourceTest.kt
assertThat(result.succeeded, `is`(true))
result as Success
- Run your test!
Step 3: Write your own local data source test
Now it's your turn.
- Copy the following code:
TasksLocalDataSourceTest.kt
@Test
fun completeTask_retrievedTaskIsComplete(){
// 1. Save a new active task in the local data source.
// 2. Mark it as complete.
// 3. Check that the task can be retrieved from the local data source and is complete.
}
- Finish the code, referring to the
saveTask_retrievesTasktest you added previously, as needed. - Run your test and confirm it passes!
The completed test is in the end_codelab_3 branch of the repository here, so that you can compare.